use log::{debug, error};
use std::{
    collections::BTreeMap,
    fs::File,
    hash::{Hash, Hasher},
    io::{BufRead, BufReader, Write},
    path::PathBuf,
};

mod cli;

#[derive(Hash, Debug, Clone)]
enum Span {
    LinkTo { href: String, spans: Vec<Span> },
    Link { href: String },
    Regular(String),
    Newline,
    Bold(String),
    Italic(String),
    Code(String),
    Space(usize),
    WithId { id: String, span: Box<Span> },
}

fn typst_escape(text: &str) -> String {
    let mut result = String::new();

    for c in text.chars() {
        match c {
            '#' | '*' | '@' => {
                result.push('\\');
                result.push(c);
            }
            _ => result.push(c),
        }
    }

    result
}

impl Span {
    #[cfg(feature = "roff")]
    fn to_plain(&self) -> String {
        match self {
            Span::LinkTo { href, spans } => {
                let mut href = href.clone();

                for each in spans {
                    href.push_str(" ");
                    href.push_str(&each.to_plain());
                }

                href
            }
            Span::Link { href } => href.clone(),
            Span::Regular(x) => x.clone(),
            Span::Newline => "\n".to_owned(),
            Span::Bold(x) => x.to_owned(),
            Span::Italic(x) => x.to_owned(),
            Span::Code(x) => x.to_owned(),
            Span::Space(n) => std::iter::repeat_n(' ', *n as usize).collect(),
            Span::WithId { span, .. } => span.to_plain(),
        }
    }

    fn to_html<W>(&self, w: &mut W, id: Option<&str>) -> Result<(), std::io::Error>
    where
        W: Write,
    {
        match self {
            Span::WithId { id, span } => span.to_html(w, Some(id)),
            Span::LinkTo { href, spans } => {
                write!(
                    w,
                    "<a href=\"{}\"",
                    html_escape::encode_double_quoted_attribute(href)
                )?;
                if let Some(id) = id {
                    write!(
                        w,
                        " id=\"{}\"",
                        html_escape::encode_double_quoted_attribute(id)
                    )?;
                }
                write!(w, ">")?;
                for span in spans {
                    span.to_html(w, None)?;
                }
                write!(w, "</a>")
            }
            Span::Link { href } => {
                write!(
                    w,
                    "<a href=\"{}\"",
                    html_escape::encode_double_quoted_attribute(href)
                )?;
                write!(w, ">")?;
                html_escape::encode_text_to_writer(href, w)?;
                write!(w, "</a>")
            }
            Span::Regular(text) => write!(w, "{}", html_escape::encode_text(text)),
            Span::Newline => write!(w, "<br/>"),
            Span::Bold(text) => write!(w, "<b>{}</b>", html_escape::encode_text(text)),
            Span::Italic(text) => write!(w, "<i>{}</i>", html_escape::encode_text(text)),
            Span::Code(text) => write!(w, "<code>{}</code>", html_escape::encode_text(text)),
            Span::Space(n) => {
                for i in 0..*n {
                    if i % 2 == 0 {
                        write!(w, " ")?;
                    } else {
                        write!(w, "&nbsp;")?;
                    }
                }
                Ok(())
            }
        }
    }

    fn to_typst<W>(&self, w: &mut W, _: Option<&str>) -> Result<(), std::io::Error>
    where
        W: Write,
    {
        match self {
            Span::WithId { id, span } => span.to_typst(w, Some(id)),
            Span::LinkTo { href, spans } => {
                write!(w, "#link({:?})[", href)?;
                for span in spans {
                    span.to_typst(w, None)?;
                }
                write!(w, "]")
            }
            Span::Link { href } => write!(w, "#link({:?})", href),
            Span::Regular(text) => write!(w, "{}", typst_escape(text)),
            Span::Newline => write!(w, "#linebreak()"),
            Span::Bold(text) => write!(w, "#text(weight: \"bold\")[{}]", text),
            Span::Italic(text) => write!(w, "#text(weight: \"italic\")[{}]", text),
            Span::Code(text) => write!(w, "#raw({:?})", text),
            Span::Space(n) => {
                for _ in 0..*n {
                    write!(w, " ")?;
                }
                Ok(())
            }
        }
    }
}

#[derive(Debug)]
struct Header {
    level: u32,
    spans: Vec<Span>,
    id: String,
}

#[derive(Debug)]
struct ListItem {
    blocks: Vec<Block>,
}

#[derive(Debug)]
struct Ol {
    items: Vec<ListItem>,
}

#[derive(Debug)]
struct Ul {
    items: Vec<ListItem>,
}

#[derive(Debug)]
enum Block {
    Header(Header),
    Ol(Ol),
    Ul(Ul),
    Paragraph { spans: Vec<Span> },
    Verbatim { lines: Vec<String> },
    Quote { blocks: Vec<Block> },
    Image { blob: String, image_path: String },
}

fn find_blob(meta: &BTreeMap<String, String>, name: &String) -> Option<PathBuf> {
    for (meta_name, meta_val) in meta.iter() {
        let Some(blob_name) = meta_name.strip_prefix("Blob") else {
            continue;
        };

        if blob_name == name {
            return Some(meta_val.into());
        }
    }

    None
}

impl Block {
    fn to_html<W>(&self, w: &mut W, meta: &BTreeMap<String, String>) -> Result<(), std::io::Error>
    where
        W: Write,
    {
        match self {
            Block::Image { image_path, blob } => {
                let image_path = find_blob(meta, blob)
                    .unwrap_or_else(|| blob.clone().into())
                    .join(image_path);

                writeln!(
                    w,
                    "<img src=\"{}\" alt=\"{}\" loading=\"lazy\" />",
                    html_escape::encode_double_quoted_attribute(&image_path.to_string_lossy()),
                    html_escape::encode_double_quoted_attribute(&image_path.to_string_lossy()),
                )
            }
            Block::Quote { blocks } => {
                writeln!(w, "<blockquote>")?;
                for block in blocks.iter() {
                    block.to_html(w, meta)?;
                }
                writeln!(w, "</blockquote>")
            }
            Block::Header(header) => header_to_html(w, header),
            Block::Paragraph { spans } => {
                write!(w, "<p>")?;
                for each in spans.iter() {
                    each.to_html(w, None)?;
                }
                writeln!(w, "</p>")
            }
            Block::Verbatim { lines } => {
                write!(w, "<pre>")?;
                for each in lines.iter() {
                    writeln!(w, "{}", html_escape::encode_text(each))?;
                }
                writeln!(w, "</pre>")
            }
            Block::Ol(Ol { items }) => {
                writeln!(w, "<ol>")?;
                items_to_html(items, w, meta)?;
                writeln!(w, "</ol>")
            }
            Block::Ul(Ul { items }) => ul_to_html(w, items, meta),
        }
    }

    fn to_typst<W>(&self, w: &mut W, meta: &BTreeMap<String, String>) -> Result<(), std::io::Error>
    where
        W: Write,
    {
        match self {
            Block::Image { image_path, blob } => {
                let image_path = find_blob(meta, blob)
                    .unwrap_or_else(|| blob.clone().into())
                    .join(image_path);

                writeln!(
                    w,
                    "#figure(image({:?}, width: 80%), caption: [{}])",
                    image_path.to_string_lossy(),
                    image_path.to_string_lossy(),
                )
            }
            Block::Quote { blocks } => {
                for block in blocks.iter() {
                    block.to_typst(w, meta)?;
                }
                writeln!(w)
            }
            Block::Header(header) => {
                for _ in 0..header.level {
                    write!(w, "=")?;
                }
                write!(w, " ")?;

                for each in header.spans.iter() {
                    each.to_typst(w, None)?;
                }
                writeln!(w, "\n")
            }
            Block::Paragraph { spans } => {
                for each in spans.iter() {
                    each.to_typst(w, None)?;
                }
                writeln!(w, "\n")
            }
            Block::Verbatim { lines } => {
                write!(w, "```")?;
                for each in lines.iter() {
                    writeln!(w, "{}", each)?;
                }
                writeln!(w, "```")
            }
            Block::Ol(Ol { items }) => {
                writeln!(w, "#list(")?;
                items_to_typst(items, w, meta)?;
                writeln!(w, ")")
            }
            Block::Ul(Ul { items }) => {
                writeln!(w, "U_LIST")?;
                items_to_typst(items, w, meta)?;
                writeln!(w, "END LIST")
            }
        }
    }
}

fn ul_to_html<W: Write>(
    w: &mut W,
    items: &Vec<ListItem>,
    meta: &BTreeMap<String, String>,
) -> Result<(), std::io::Error> {
    writeln!(w, "<ul>")?;
    items_to_html(items, w, meta)?;
    writeln!(w, "</ul>")
}

fn header_to_html<W: Write>(w: &mut W, header: &Header) -> Result<(), std::io::Error> {
    write!(
        w,
        "<h{} id=\"{}\">",
        header.level,
        html_escape::encode_double_quoted_attribute(&header.id)
    )?;
    for each in header.spans.iter() {
        each.to_html(w, None)?;
    }
    write!(w, "<a href=\"#toc-{}\"> ¶</a> ", header.id)?;
    writeln!(w, "</h{}>", header.level)
}

fn html_typst<W: Write>(_: &BTreeMap<String, String>, w: &mut W) -> Result<(), std::io::Error> {
    writeln!(
        w,
        r#"#set heading(numbering: "1.")
#outline()")?
#pagebreak()
"#
    )?;

    Ok(())
}

fn html_header<W: Write>(
    config: &BTreeMap<String, String>,
    w: &mut W,
) -> Result<(), std::io::Error> {
    writeln!(w, "<!DOCTYPE html>")?;
    writeln!(w, "<html><head>")?;

    writeln!(w, "\t<meta charset=\"UTF-8\" />")?;
    writeln!(
        w,
        "\t<meta name\"generator\" content=\"ldoc - {}\" />",
        env!("CARGO_PKG_VERSION"),
    )?;
    writeln!(w, "\t<meta name\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=yes\" />")?;

    if let Some(title) = config.get("Title") {
        writeln!(w, "\t<title>{title}</title>")?;
    }

    writeln!(w, "\t<style>")?;
    writeln!(w)?;
    writeln!(w, "{}", include_str!("html.css"))?;
    writeln!(w)?;
    writeln!(w, "\t</style>")?;

    writeln!(w, "</head><body><div id={:?}>", "content")?;

    Ok(())
}

fn html_footer<W: Write>(_: &BTreeMap<String, String>, w: &mut W) -> Result<(), std::io::Error> {
    writeln!(w, "</div></body></html>")?;
    Ok(())
}

fn items_to_html<W: Write>(
    items: &Vec<ListItem>,
    w: &mut W,
    meta: &BTreeMap<String, String>,
) -> Result<(), std::io::Error> {
    let tight_list = items
        .iter()
        .all(|item| matches!(item.blocks.as_slice(), [Block::Paragraph { .. }]));

    for item in items {
        write!(w, "<li>")?;
        if tight_list {
            if let [Block::Paragraph { spans }] = item.blocks.as_slice() {
                for each in spans {
                    each.to_html(w, None)?;
                }
            }
        } else {
            for each in item.blocks.iter() {
                each.to_html(w, meta)?;
            }
        }
        writeln!(w, "</li>")?;
    }

    Ok(())
}

fn items_to_typst<W: Write>(
    items: &Vec<ListItem>,
    w: &mut W,
    meta: &BTreeMap<String, String>,
) -> Result<(), std::io::Error> {
    let tight_list = items
        .iter()
        .all(|item| matches!(item.blocks.as_slice(), [Block::Paragraph { .. }]));

    for item in items {
        write!(w, "[")?;
        if tight_list {
            if let [Block::Paragraph { spans }] = item.blocks.as_slice() {
                for each in spans {
                    each.to_typst(w, None)?;
                }
            }
        } else {
            for each in item.blocks.iter() {
                each.to_typst(w, meta)?;
            }
        }
        writeln!(w, "],")?;
    }

    Ok(())
}

struct Parser<'meta, I, S>
where
    I: Iterator<Item = Result<S, ()>>,
    S: AsRef<str>,
{
    path: PathBuf,
    lines: std::iter::Peekable<I>,
    meta: &'meta mut BTreeMap<String, String>,
    line_num: usize,
}

fn parser_from_file(
    path: PathBuf,
    meta: &mut BTreeMap<String, String>,
) -> Result<Parser<impl Iterator<Item = Result<String, ()>>, String>, ()> {
    let reader =
        File::open(&path).map_err(|err| error!("failed to open `{}': {}", path.display(), err))?;

    let reader = BufReader::new(reader);

    let parser = Parser::new(
        reader
            .lines()
            .map(|x| x.map_err(|err| error!("failed to read line from reader: {err}"))),
        path.clone(),
        meta,
    );

    Ok(parser)
}

#[test]
fn test_cmd_parse() {
    let mut meta = BTreeMap::new();

    let mut parser = Parser::new(
        ["Header 1 This is header", "Header 2  This is header"]
            .iter()
            .map(Ok),
        std::path::Path::new("test.ldoc").to_owned(),
        &mut meta,
    );

    match parser.peek_cmd() {
        Ok(Cmd::Header { level: 1, content }) => {
            assert_eq!(content, "This is header");
        }
        cmd => panic!("invalid cmd: {cmd:?}"),
    };

    parser.consume_line();

    match parser.peek_cmd() {
        Ok(Cmd::Header { level: 2, content }) => {
            assert_eq!(content, "This is header");
        }
        cmd => panic!("invalid cmd: {cmd:?}"),
    };

    parser.consume_line();
}

fn split_token(text: &str) -> (&str, &str) {
    let text = text.trim_start_matches(is_whitespace);

    let Some(n) = text.find(is_whitespace) else {
        return (text, "");
    };

    let (head, tail) = text.split_at(n);

    (head, tail.trim_start_matches(is_whitespace))
}

impl<'meta, I, S> Parser<'meta, I, S>
where
    I: Iterator<Item = Result<S, ()>>,
    S: AsRef<str>,
{
    fn new(lines: I, path: PathBuf, meta: &'meta mut BTreeMap<String, String>) -> Self {
        Self {
            path,
            lines: lines.peekable(),
            meta,
            line_num: 1,
        }
    }

    fn parse_block(&mut self, cmd: Cmd) -> Result<Block, ()> {
        Ok(match cmd {
            Cmd::P(content) => {
                self.consume_line();

                let spans = self.parse_p(content)?;

                Block::Paragraph { spans }
            }
            Cmd::Image(path) => {
                self.consume_line();

                let Some((blob, image_path)) = path.split_once(":") else {
                    error!("image path not starts with `BLOB:': {}", path);
                    return Err(());
                };

                Block::Image {
                    blob: blob.into(),
                    image_path: image_path.into(),
                }
            }
            Cmd::Header { level, content } => {
                self.consume_line();

                let header = self.parse_header(level, content)?;
                Block::Header(header)
            }
            Cmd::Verbatim { indent, token } => {
                self.consume_line();

                let lines = self.parse_verbatim(indent, token)?;
                Block::Verbatim { lines }
            }
            Cmd::Ol(content) => {
                self.consume_line();

                let items = self.parse_list(content)?;
                Block::Ol(Ol { items })
            }
            Cmd::Ul(content) => {
                self.consume_line();

                let items = self.parse_list(content)?;
                Block::Ul(Ul { items })
            }

            Cmd::Quote => {
                self.consume_line();

                let blocks = self.parse_blocks_until_end()?;

                Block::Quote { blocks }
            }

            _ => {
                error!("can not parse block starts with command: {cmd:?}");
                return Err(());
            }
        })
    }

    fn parse_blocks_until_end(&mut self) -> Result<Vec<Block>, ()> {
        let mut blocks = vec![];

        loop {
            let cmd = self.peek_cmd()?;

            match cmd {
                Cmd::Eof => {
                    error!("reach eof before end");
                    return Err(());
                }
                Cmd::End => {
                    self.consume_line();
                    break;
                }
                Cmd::Empty => {
                    self.consume_line();
                    continue;
                }
                Cmd::Include(path) => {
                    self.parse_include(path, &mut blocks)?;
                    continue;
                }
                _ => {
                    let block = self.parse_block(cmd)?;
                    blocks.push(block);
                }
            }
        }

        Ok(blocks)
    }

    fn parse_include(&mut self, path: String, blocks: &mut Vec<Block>) -> Result<(), ()> {
        self.consume_line();

        let Some(parent) = self.path.parent() else {
            error!(
                "no parent director of current file: `{}'",
                self.path.display()
            );
            return Err(());
        };

        let path = parent.join(path);

        debug!("new parser: {path:?}");

        let mut parser = parser_from_file(path, self.meta)?;

        blocks.extend(parser.parse_blocks()?);

        Ok(())
    }

    fn parse_blocks(&mut self) -> Result<Vec<Block>, ()> {
        self.try_parse_blocks()
            .map_err(|_| error!("error at {}:{}", self.path.display(), self.line_num))
    }

    fn try_parse_blocks(&mut self) -> Result<Vec<Block>, ()> {
        let mut blocks = vec![];

        loop {
            let cmd = self.peek_cmd()?;

            match cmd {
                Cmd::Eof => break,
                Cmd::Empty => {
                    self.consume_line();
                    continue;
                }
                Cmd::Include(path) => {
                    self.parse_include(path, &mut blocks)?;
                    continue;
                }
                Cmd::Meta { name, value } => {
                    self.meta.insert(name, value);
                    self.consume_line();
                    continue;
                }
                _ => {
                    let block = self.parse_block(cmd)?;
                    blocks.push(block);
                }
            }
        }

        Ok(blocks)
    }

    fn peek_cmd(&mut self) -> Result<Cmd, ()> {
        let Some(line) = self.lines.peek() else {
            debug!("peek cmd: eof");
            return Ok(Cmd::Eof);
        };

        let mut line: &str = line.as_ref().map(AsRef::as_ref).map_err(|_| ())?.trim_end();

        if line.is_empty() {
            debug!("peek cmd: empty");
            return Ok(Cmd::Empty);
        }

        let mut indent = 0;

        while let Some(rest) = line.strip_prefix('\t') {
            line = rest;
            indent += 1;
        }

        let (cmd, rest) = split_token(line);

        let cmd = match cmd {
            "Header" => {
                let (level, content) = split_token(rest);

                let Ok(level) = level.parse() else {
                    error!("can not parse level of header: {level}");
                    return Err(());
                };

                Cmd::Header {
                    level,
                    content: content.to_owned(),
                }
            }
            "Meta" => {
                let (name, value) = split_token(rest);

                Cmd::Meta {
                    name: name.to_owned(),
                    value: value.to_owned(),
                }
            }
            "Verbatim" => Cmd::Verbatim {
                indent,
                token: rest.to_owned(),
            },
            "Include" => Cmd::Include(rest.to_owned()),
            "P" => Cmd::P(rest.to_owned()),
            "\\" => Cmd::Continue(rest.to_owned()),
            "Ul" => Cmd::Ul(rest.to_owned()),
            "Ol" => Cmd::Ol(rest.to_owned()),
            "Li" => Cmd::Li(rest.to_owned()),
            "B" => Cmd::B(rest.to_owned()),
            "I" => Cmd::I(rest.to_owned()),
            "C" => Cmd::C(rest.to_owned()),
            "Link" => Cmd::Link(rest.to_owned()),
            "Image" => Cmd::Image(rest.to_owned()),
            "Space" => {
                let space = if rest.is_empty() {
                    1
                } else {
                    rest.parse()
                        .map_err(|err| error!("failed to parse space count: {err}"))?
                };
                Cmd::Space(space)
            }
            "Quote" => {
                if !rest.is_empty() {
                    error!("command Quote can't accept content argument");
                    return Err(());
                }
                Cmd::Quote
            }
            "Nl" => {
                if !rest.is_empty() {
                    error!("command Nl can't accept content argument");
                    return Err(());
                }
                Cmd::Nl
            }
            "End" => Cmd::End,
            _ => {
                error!("invalid command: {cmd}");
                return Err(());
            }
        };

        debug!("peek command: {cmd:?}");

        Ok(cmd)
    }

    fn parse_p(&mut self, content: String) -> Result<Vec<Span>, ()> {
        let mut spans = vec![Span::Regular(content)];

        self.parse_spans(&mut spans);

        Ok(spans)
    }

    fn parse_spans(&mut self, spans: &mut Vec<Span>) {
        loop {
            match self.peek_cmd() {
                Ok(Cmd::Continue(content)) => {
                    spans.push(Span::Regular(content.to_owned()));
                    self.consume_line();
                }
                Ok(Cmd::Nl) => {
                    spans.push(Span::Newline);
                    self.consume_line();
                }
                Ok(Cmd::B(content)) => {
                    spans.push(Span::Bold(content));
                    self.consume_line();
                }
                Ok(Cmd::I(content)) => {
                    spans.push(Span::Italic(content));
                    self.consume_line();
                }
                Ok(Cmd::C(content)) => {
                    spans.push(Span::Code(content));
                    self.consume_line();
                }
                Ok(Cmd::Link(content)) => {
                    spans.push(Span::Link { href: content });
                    self.consume_line();
                }
                Ok(Cmd::Space(n)) => {
                    spans.push(Span::Space(n));
                    self.consume_line();
                }
                Ok(Cmd::Empty) => {
                    self.consume_line();
                }
                _ => break,
            }
        }
    }

    fn consume_line(&mut self) {
        self.lines.next();
        self.line_num += 1;
    }

    /// Parse header.
    fn parse_header(&mut self, level: u32, content: String) -> Result<Header, ()> {
        let mut header = Header {
            level,
            spans: vec![Span::Regular(content)],
            id: String::new(),
        };

        self.parse_spans(&mut header.spans);

        Ok(header)
    }

    /// Parse verbatim block.
    fn parse_verbatim(&mut self, indent: u32, token: String) -> Result<Vec<String>, ()> {
        let mut lines = vec![];

        loop {
            debug!("parse verbatim line");

            let Some(line) = self.lines.peek() else {
                error!("reach eof before end of verbatim: {token}");
                return Err(());
            };

            let mut line: &str = line.as_ref().map(AsRef::as_ref).map_err(|_| ())?.trim_end();

            if line.is_empty() {
                self.consume_line();
                lines.push(String::new());
                continue;
            }

            for _ in 0..indent {
                if let Some(rest) = line.strip_prefix('\t') {
                    line = rest;
                } else {
                    error!("inner of verbatim blocks does not have enough indentation");
                    return Err(());
                }
            }

            if let Some(rest) = line.strip_prefix('\t') {
                lines.push(rest.to_owned());
                self.consume_line();
            } else if token.is_empty() && line == "End" || line == format!("End {token}") {
                self.consume_line();
                break;
            } else {
                error!("expect end of verbatim block: `{line}', current command: `{token}'");
                return Err(());
            }
        }

        Ok(lines)
    }

    /// Parse ordered list or unoredered list.
    fn parse_list(&mut self, content: String) -> Result<Vec<ListItem>, ()> {
        let mut items = vec![ListItem {
            blocks: self.parse_item(content)?,
        }];

        loop {
            match self.peek_cmd()? {
                Cmd::End => {
                    self.consume_line();
                    break;
                }
                Cmd::Eof => {
                    error!("reach eof before end of list");
                    return Err(());
                }
                Cmd::Li(content) => {
                    self.consume_line();
                    items.push(ListItem {
                        blocks: self.parse_item(content)?,
                    });
                }
                cmd => {
                    error!("expect a list item, found: {:?}", cmd);
                    return Err(());
                }
            }
        }

        Ok(items)
    }

    fn parse_item(&mut self, content: String) -> Result<Vec<Block>, ()> {
        let mut spans = vec![Span::Regular(content)];
        self.parse_spans(&mut spans);

        let mut blocks = vec![Block::Paragraph { spans }];

        loop {
            match self.peek_cmd()? {
                Cmd::End => {
                    break;
                }
                Cmd::Empty => {
                    self.consume_line();
                    continue;
                }
                Cmd::Eof => {
                    error!("reach eof before end of ordered list");
                    return Err(());
                }
                Cmd::Li(_) => {
                    break;
                }
                cmd => {
                    blocks.push(self.parse_block(cmd)?);
                }
            }
        }

        Ok(blocks)
    }
}

fn is_whitespace(c: char) -> bool {
    c == ' ' || c == '\t'
}

#[derive(Debug)]
enum Cmd {
    // End of file, or end of input.
    Eof,

    // Not a command, skip it.
    Empty,

    // Read commands from another file.
    //
    // Include PATH
    Include(String),

    // Header.
    //
    // Header N TEXT
    Header { level: u32, content: String },

    // Verbatim block
    //
    // Verbatim
    //     ....
    // End
    Verbatim { indent: u32, token: String },

    // Quoted block
    //
    // Quote
    //     ...
    // End
    Quote,

    // Newline
    Nl,

    // Paragraph.
    //
    // P TEXT
    P(String),

    // Continue text.
    //
    // \ TEXT
    Continue(String),

    // Ordered list.
    //
    // Ol ...
    // Li ...
    // Li ...
    // End
    Ol(String),

    // Unordered list.
    //
    // Ul ...
    // Li ...
    // Li ...
    // End
    Ul(String),

    // Item in Ul or Ol.
    Li(String),

    // Bold text.
    //
    // B TEXT
    B(String),

    // Itailc text.
    //
    // I TEXT
    I(String),

    // Inline code.
    //
    // C TEXT
    C(String),

    // Write some spaces.
    //
    // Space N
    //
    // THIS IS NOT STABLE, I want a better way to control spaces.
    Space(usize),

    // Set metadata.
    //
    // Meta NAME VALUE
    Meta { name: String, value: String },

    // Link PATH
    Link(String),

    // External image.
    //
    // Image PATH
    Image(String),

    // End of a block.
    End,
}

fn try_main() -> Result<(), ()> {
    env_logger::init_from_env("LDOC_LOG");

    let cli = cli::parse_cli()?;

    let mut meta = BTreeMap::new();
    let mut parser = parser_from_file(cli.path.clone(), &mut meta)?;

    let mut blocks = parser.parse_blocks()?;

    for (name, v) in cli.meta.into_iter() {
        parser.meta.insert(name, v);
    }

    let toc = make_toc(parser.meta, &mut blocks);

    let output = cli
        .output
        .unwrap_or_else(|| cli.path.with_extension("html"));

    let mut writer = File::create(&output)
        .map_err(|err| error!("failed to open `{}': {err}", output.display()))?;

    match output.extension().and_then(|s| s.to_str()) {
        Some("html") => {
            to_html(&mut writer, parser.meta, &toc, &blocks)
                .map_err(|err| error!("failed to write to `{}': {err}", output.display()))?;
        }
        Some("typ") => {
            to_typst(&mut writer, parser.meta, &toc, &blocks)
                .map_err(|err| error!("failed to write to `{}': {err}", output.display()))?;
        }
        ext => {
            error!("can not understand extension: {ext:?}");
            return Err(());
        }
    }

    Ok(())
}

struct Toc {
    header: Header,
    list: Ol,
}

impl Toc {
    fn to_html<W: Write>(
        &self,
        w: &mut W,
        meta: &BTreeMap<String, String>,
    ) -> Result<(), std::io::Error> {
        header_to_html(w, &self.header)?;
        ul_to_html(w, &self.list.items, meta)?;
        Ok(())
    }
}

#[derive(Debug)]
struct TocNode<'header> {
    header: &'header Header,
    children: Vec<TocNode<'header>>,
}

impl TocNode<'_> {
    fn to_list_item(&self) -> ListItem {
        let mut ol = Ol { items: vec![] };

        for each in &self.children {
            let item = each.to_list_item();

            ol.items.push(item);
        }

        let header = Block::Paragraph {
            spans: vec![Span::WithId {
                id: format!("toc-{}", self.header.id),
                span: Box::new(Span::LinkTo {
                    href: format!("#{}", self.header.id),
                    spans: self.header.spans.clone(),
                }),
            }],
        };

        let blocks = if ol.items.is_empty() {
            vec![header]
        } else {
            vec![header, Block::Ol(ol)]
        };

        ListItem { blocks }
    }
}

fn make_toc(_meta: &BTreeMap<String, String>, blocks: &mut [Block]) -> Toc {
    let header: Header = Header {
        level: 1,
        spans: vec![Span::Regular("目录".to_string())],
        id: "header-toc".to_owned(),
    };

    let dummy = Header {
        level: 0,
        spans: vec![],
        id: String::new(),
    };

    let mut header0 = TocNode {
        header: &dummy,
        children: vec![],
    };

    // If stack is empty, pretend header0 at the stack top.
    let mut stack: Vec<TocNode> = vec![];

    for (i, each) in blocks.iter_mut().enumerate() {
        let Block::Header(header) = each else {
            continue;
        };

        if header.level >= 3 {
            continue;
        }

        if header.id.is_empty() {
            let mut h = std::hash::DefaultHasher::new();

            h.write_usize(i);
            header.spans.hash(&mut h);

            header.id = format!("header-{:x}", h.finish());
        }

        while header.level <= stack.last().unwrap_or(&header0).header.level {
            let top = stack.pop().unwrap();

            stack.last_mut().unwrap_or(&mut header0).children.push(top);
        }

        stack.push(TocNode {
            header,
            children: vec![],
        });
    }

    while let Some(top) = stack.pop() {
        stack.last_mut().unwrap_or(&mut header0).children.push(top);
    }

    let list = Ol {
        items: header0
            .children
            .iter()
            .map(|node| node.to_list_item())
            .collect(),
    };

    Toc { header, list }
}

fn to_html(
    writer: &mut File,
    meta: &BTreeMap<String, String>,
    toc: &Toc,
    blocks: &[Block],
) -> Result<(), std::io::Error> {
    html_header(meta, writer)?;

    toc.to_html(writer, meta)?;

    for block in blocks.iter() {
        block.to_html(writer, meta)?;
    }

    html_footer(meta, writer)?;

    Ok(())
}

fn to_typst(
    writer: &mut File,
    meta: &BTreeMap<String, String>,
    _: &Toc,
    blocks: &[Block],
) -> Result<(), std::io::Error> {
    html_typst(meta, writer)?;

    for block in blocks.iter() {
        block.to_typst(writer, meta)?;
    }

    Ok(())
}

fn main() {
    if try_main().is_err() {
        std::process::exit(1);
    }
}
